import java.util.ArrayList;
import java.lang.Math;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author jesus, nate, mike
 */
public class Features {

    /* prints to command line the values from functions */
    public static void printStrokeValues(Stroke s){
        System.out.println("[" + timeStroke(s) + "] , [" + avgPointTime(s) +  "] , [" + euclideanLength(s) +  "] , [" + strokeLength(s) +  "] , [" + manhattanDistance(s) + "] , [" + euclideanSpeed(s) + "] , [" + totalSpeed(s) + "]");
        System.out.println("[" + manhattanSpeed(s) + "] , [" + endpointCurvature(s) +  "] , [" + totalCurvature(s) + "] , [" + absoluteCurvature(s) +  "]");
        System.out.println("[" + strokeJerkiness(s) + "] , [" + startToEndPressureDifference(s) + "] , [" + middleToEndsPressureDifference(s) + "]");
        System.out.println("[" + averagePressure(s) + "] , [" + pressureTime(s) +  "] , [" + weight(s) +  "] , [" + firstEighthVersusLastThirdTime(s) +  "] , [" + averageTiltX(s) + "] , [" + averageTiltY(s) + "]");
        return;
    }

    /* prints headers for last function */
    public static void printStrokeValueHeaders(){
        System.out.println("timeStroke, avgPointTime, euclideanLength, strokeLength, manhattanDistance, euclideanSpeed, totalSpeed");
        System.out.println("manhattanSpeed, endpointCurvature, totalCurvature, absoluteCurvature");
        System.out.println("strokeJerkiness, startToEndPressureDifference, middleToEndsPressureDifference");
        System.out.println("averagePressure, pressureTime, weight, firstEighthVersusLastThirdTime, averageTiltX, averageTiltY");
    }

    /* time between first and last points of a stroke */
    public static double timeStroke(Stroke s) {
        int n = s.dataPoints.size();
        if(n < 2)
            return 0;
        Point p1 = s.dataPoints.get(0);
        Point p2 = s.dataPoints.get(n-1);
        return p2.time - p1.time;
    }

    /* average time between stroke points */
    public static double avgPointTime(Stroke s) {
        ArrayList<Double> times = new ArrayList<Double>();
        for(int i = 0; i < s.dataPoints.size(); ++i) {
            times.add(s.dataPoints.get(i).time);
        }
        double avg = 0;
        for(int i = 0; i < times.size(); ++i) {
            avg += times.get(i);
        }
        avg /= times.size();

        return avg;
    }

    /* distance between two endpoints of stroke */
    public static double euclideanLength(Stroke s) {
        int n = s.dataPoints.size();
        Point p1 = s.dataPoints.get(0);
        Point p2 = s.dataPoints.get(n-1);
        double x_diff = p2.x_coor - p1.x_coor;
        double y_diff = p2.y_coor - p1.y_coor;
        return Math.sqrt(x_diff * x_diff + y_diff * y_diff);
    }

    /* total length of stroke */
    public static double strokeLength(Stroke s) {
        double total_length = 0;
        for (int i = 0; i < (s.dataPoints.size() - 2); ++i) {
            Point p1 = s.dataPoints.get(i);
            Point p2 = s.dataPoints.get(i+1);
            double x_diff = p2.x_coor - p1.x_coor;
            double y_diff = p2.y_coor - p1.y_coor;
            total_length += Math.sqrt(x_diff * x_diff + y_diff * y_diff);
        }
        return total_length;
    }

    /* Manhattan distance between endpoints */
    public static double manhattanDistance(Stroke s) {
        int n = s.dataPoints.size();
        Point p1 = s.dataPoints.get(0);
        Point p2 = s.dataPoints.get(n-1);
        double x_diff = p2.x_coor - p1.x_coor;
        double y_diff = p2.y_coor - p1.y_coor;
        return x_diff + y_diff;
    }

    /* Euclidean speed of stroke */
    public static double euclideanSpeed(Stroke s) {
        return euclideanLength(s) / timeStroke(s);
    }

    /* total speed of stroke */
    public static double totalSpeed(Stroke s) {
        return strokeLength(s) / timeStroke(s);
    }

    /* Manhattan speed of stroke */
    public static double manhattanSpeed(Stroke s) {
        return manhattanDistance(s) / timeStroke(s);
    }

    /* endpoint curvature of stroke */
    public static double endpointCurvature(Stroke s) {
        int n = s.dataPoints.size();
        if(n < 2)
            return -1.0;
        Point start1 = s.dataPoints.get(0);
        Point start2 = s.dataPoints.get(1);
        Point end1 = s.dataPoints.get(n-2);
        Point end2 = s.dataPoints.get(n-1);
        double startAngle = Math.atan2(start2.y_coor - start1.y_coor, start2.x_coor - start1.x_coor);
        double endAgle = Math.atan2(end2.y_coor - end1.y_coor, end2.x_coor - end1.x_coor);
        return Math.abs(endAgle - startAngle);
    }
    
    /* total curvature of stroke */
    public static double totalCurvature(Stroke s) {
        int n = s.dataPoints.size();
        if(n < 3)
            return 0;
        double curvature = 0;
        for(int i = 0; i < (n - 2); ++i) {
            Point p1 = s.dataPoints.get(i);
            Point p2 = s.dataPoints.get(i+1);
            Point p3 = s.dataPoints.get(i+2);
            double angle1 = Math.atan2(p2.y_coor - p1.y_coor,
                                       p2.x_coor - p1.x_coor);
            double angle2 = Math.atan2(p3.y_coor - p2.y_coor,
                                       p3.x_coor - p2.x_coor);
            curvature += (angle1 - angle2);
        }
        return curvature;
    }

    /* total absolute curvature of stroke */
    public static double absoluteCurvature(Stroke s) {
        int n = s.dataPoints.size();
        if(n < 3)
            return 0;
        double absCurvature = 0;
        for(int i = 0; i < (n - 2); ++i) {
            Point p1 = s.dataPoints.get(i);
            Point p2 = s.dataPoints.get(i+1);
            Point p3 = s.dataPoints.get(i+2);
            double angle1 = Math.atan2(p2.y_coor - p1.y_coor,
                                       p2.x_coor - p1.x_coor);
            double angle2 = Math.atan2(p3.y_coor - p2.y_coor,
                                       p3.x_coor - p2.x_coor);
            absCurvature += Math.abs(angle1 - angle2);
        }
        return absCurvature;
    }

    /* changes in curvature, return indices of changes in curvature */
    public static ArrayList<Integer> curvatureChanges(Stroke s) {
        ArrayList<Integer> curvChanges = new ArrayList<Integer>();
        int n = s.dataPoints.size();
        if(n < 15)
            return curvChanges;
        double prevSign = 0.0;
        for(int i = 0; i < (n - 11); i+=5) {
            Point p1 = s.dataPoints.get(i);
            Point p2 = s.dataPoints.get(i+5);
            Point p3 = s.dataPoints.get(i+10);
            double angle1 = Math.atan2(p2.y_coor - p1.y_coor,
                                       p2.x_coor - p1.x_coor);
            double angle2 = Math.atan2(p3.y_coor - p2.y_coor,
                                       p3.x_coor - p2.x_coor);
            double sign = Math.signum(angle1 - angle2);
            if((prevSign != sign) && (i != 0))
                curvChanges.add(i);
            prevSign = sign;
        }
        curvChanges.add(n);
        return curvChanges;
    }

    /* jerkiness of stroke */
    public static double strokeJerkiness(Stroke s) {
        return totalCurvature(s) / s.dataPoints.size();
    }

    /* pressure difference between the first and last points of a stroke */
    public static double startToEndPressureDifference(Stroke s) {
        int n = s.dataPoints.size();
        Point p1 = s.dataPoints.get(0);
        Point p2 = s.dataPoints.get(n-1);
        return (double) p1.pressure - p2.pressure;
    }
    
    /* pressure difference between the first/last average and middle point of a stroke */
    public static double middleToEndsPressureDifference(Stroke s) {
        int n = s.dataPoints.size();
        Point p1 = s.dataPoints.get(0);
        Point p2 = s.dataPoints.get((int)(n/2));
        Point p3 = s.dataPoints.get(n-1);
        return (double) p2.pressure - (p1.pressure + p3.pressure / 2.0);
    }

    /* average pressure of a stroke */
    public static double averagePressure(Stroke s) {
        ArrayList<Float> pressures = new ArrayList<Float>();
        for(int i = 0; i < s.dataPoints.size(); ++i) {
            pressures.add(s.dataPoints.get(i).pressure);
        }
        float average = 0;
        for(int i=0; i < pressures.size(); ++i){
            average += pressures.get(i);
        }
        average /= pressures.size();

        return (double) average;
    }
    
    /* sum of pressure differences */
    public static double pressureDiff(Stroke s) {
        int n = s.dataPoints.size();
        if(n < 3)
            return 0;
        float pressures = 0;
        for(int i = 0; i < (n - 2); ++i) {
            Point p1 = s.dataPoints.get(i);
            Point p2 = s.dataPoints.get(i+1);
            Point p3 = s.dataPoints.get(i+2);
            float diff1 = p2.pressure - p1.pressure;
            float diff2 = p3.pressure - p2.pressure;
            pressures += (diff2 - diff1);
        }
        return (double) pressures;
    }
    
    /* sum of absolute value of pressure differences */
    public static double absPressureDiff(Stroke s) {
        int n = s.dataPoints.size();
        if(n < 3)
            return 0;
        float absPressures = 0;
        for(int i = 0; i < (n - 2); ++i) {
            Point p1 = s.dataPoints.get(i);
            Point p2 = s.dataPoints.get(i+1);
            Point p3 = s.dataPoints.get(i+2);
            float diff1 = p2.pressure - p1.pressure;
            float diff2 = p3.pressure - p2.pressure;
            absPressures += Math.abs(diff2 - diff1);
        }
        return (double) absPressures;
    }

    /* pressure per time */
    public static double pressureTime(Stroke s) {
        return averagePressure(s) / timeStroke(s);
    }

    /* weight of stroke: length times average pressure */
    public static double weight(Stroke s) {
        return averagePressure(s) * strokeLength(s);
    }

    /* time of first eighth versus last third */
    public static double firstEighthVersusLastThirdTime(Stroke s) {
        Stroke first = new Stroke(s,1,8);
        Stroke last = new Stroke(s,3,3);
        return timeStroke(first) - timeStroke(last);
    }
    
	/* average X Tilt of a stroke */
    public static double averageTiltX(Stroke s) {
        ArrayList<Float> tilt = new ArrayList<Float>();
        for(int i = 0; i < s.dataPoints.size(); i++) {
            tilt.add(s.dataPoints.get(i).tilt_x);
        }
        float average = 0;
        for(int i=0; i < tilt.size(); i++) {
            average += tilt.get(i);
        }
        average /= tilt.size();
        return (double) average;
    }
	
	/* average Y Tilt of a stroke */
    public static double averageTiltY(Stroke s) {
        ArrayList <Float> tilt = new ArrayList<Float>();
        for(int i = 0; i < s.dataPoints.size(); i++) {
            tilt.add(s.dataPoints.get(i).tilt_y);
        }
        float average = 0;
        for(int i=0; i < tilt.size(); i++) {
            average += tilt.get(i);
        }
        average /= tilt.size();
        return (double) average;
    }
	
    /* box size of canvas covered by curve  (Pixels)*/
    public static double sizeBoundingBox(Stroke s) {
        double xMin, xMax, yMin, yMax;
        xMin=xMax=yMin=yMax=0;
        double x=0, y=0;
        double size;
        for(int i = 0; i < s.dataPoints.size(); i++)
        {
            x = s.dataPoints.get(i).x_coor;
            y = s.dataPoints.get(i).y_coor;
            if(i==0)
            {
                xMin = xMax = x;
                yMin = yMax = y;
            }
            if(x < xMin)
                xMin=x;
            if(x > xMax)
                xMax=x;
            if(y < yMin)
                yMin=y;
            if(y > yMax)
                yMax=y;
        }
        double xD= xMax - xMin;
        double yD= yMax - yMin;
        return xD*yD;
    }
    /* angle of box of canvas covered by curve*/
    public static double angleBoundingBox(Stroke s) {
        double xMin, xMax, yMin, yMax;
        xMin=xMax=yMin=yMax=0;
        double x, y;
        double theta;
        for(int i = 0; i < s.dataPoints.size(); i++)
        {
            x = s.dataPoints.get(i).x_coor;
            y = s.dataPoints.get(i).y_coor;
            if (i==0)
            {
                xMin = x;
                xMax = x;
                yMin = y;
                yMax = y;
            }
            if(x < xMin)
                    xMin=x;
            if(x > xMax)
                    xMax=x;
            if(y < yMin)
                    yMin=y;
            if(y > yMax)
                    yMax=y;
        }
        double xD= xMax - xMin;
        double yD= (yMax - yMin);
        theta= Math.atan2(yD, xD);
        return theta;
    }
    /*Overal Average Tilt   */
    public static double averageTilt(Stroke s) {
        ArrayList <Double> tilt = new ArrayList<Double>();
        double x=0;
        double y=0;
        for(int i = 0; i < s.dataPoints.size(); i++) {
           /* x=(double)s.dataPoints.get(i).tilt_x;
            y=(double)s.dataPoints.get(i).tilt_y;
            x=Math.cos(x);
            y=Math.cos(y);
            tilt.add(Math.sqrt((x*x)+(y*y)));*/
            tilt.add((double)(s.dataPoints.get(i).tilt_x + s.dataPoints.get(i).tilt_y)/2.0);
        }

        double average=0;
        for(int i=0; i < tilt.size(); i++) {
            average += tilt.get(i);
                }
        average /= tilt.size();
        return average;
    } 
	
    /* Top speed   */
    public static double topSpeed(Stroke s) {
       double topSpeed=0;
       double speed=0;
       double time=0;
       double dist=0;
       double x1, x2;
       double y1, y2;
       x1=x2=y1=y2=0;
       for(int i = 0; i < s.dataPoints.size()-5; i++) {
            x1 = s.dataPoints.get(i).x_coor;
            y1 = s.dataPoints.get(i).y_coor;
            x2 = s.dataPoints.get(i+5).x_coor;
            y2 = s.dataPoints.get(i+5).y_coor;
            time= s.dataPoints.get(i+5).time- s.dataPoints.get(i).time;
            dist= Math.sqrt(((y2-y1)*(y2-y1))+((x2-x1)*(x2-x1)));
            speed=dist/time;
            if(speed > topSpeed)
                topSpeed=speed;
        }
       return topSpeed;
    }

    /* Slowest speed   */
    public static double slowestSpeed(Stroke s) {
        double slowSpeed=0;
        double speed;
        double time=0;
        double dist=0;
        double x1, x2;
        double y1, y2;
        x1=x2=y1=y2=0;
        for(int i = 0; i < s.dataPoints.size()-1; i++) {
            x1 = s.dataPoints.get(i).x_coor;
            y1 = s.dataPoints.get(i).y_coor;
            x2 = s.dataPoints.get(i+1).x_coor;
            y2 = s.dataPoints.get(i+1).y_coor;
            time= s.dataPoints.get(i+1).time- s.dataPoints.get(i).time;
            dist= Math.sqrt(((y2-y1)*(y2-y1))+((x2-x1)*(x2-x1)));
            speed=dist/time;
            if(i==0)
                slowSpeed=speed;
            if(speed < slowSpeed)
                slowSpeed=speed;
        }
        return slowSpeed;
    }

    /* Beginning Stroke Angle   */
    public static double beginningAngle(Stroke s){
        double theta=0;
        double x1, x2;
        double y1, y2;
        double xD;
        double yD;
        x1=x2=y1=y2=0;
        if(s.dataPoints.size()<6)
            return 0;
        x1 = s.dataPoints.get(0).x_coor;
        y1 = s.dataPoints.get(0).y_coor;
        x2 = s.dataPoints.get(5).x_coor;
        y2 = s.dataPoints.get(5).y_coor;
        xD=x2-x1;
        yD=(y2-y1)*(-1);
        theta= Math.atan2(yD, xD);
        return theta;
    }

    /* Aspect Ratio of Width to 1 Height*/
    public static double aspectRatio(Stroke s){
        double xMin, xMax, yMin, yMax;
        xMin=xMax=yMin=yMax=0;
        double x, y;
        double ratio=0;
        for(int i = 0; i < s.dataPoints.size(); i++)
        {
            x = s.dataPoints.get(i).x_coor;
            y = s.dataPoints.get(i).y_coor;
            if (i==0)
            {
                xMin = x;
                xMax = x;
                yMin = y;
                yMax = y;
            }
            if(x < xMin)
                    xMin=x;
            if(x > xMax)
                    xMax=x;
            if(y < yMin)
                    yMin=y;
            if(y > yMax)
                    yMax=y;
        }
        double xD= xMax - xMin;
        double yD= yMax - yMin;
        ratio = Math.abs(xD/yD);
        return ratio;
    }
    /* Aspect Ratio of Width to 1 Height*/
    public static double aspectRatioLog(Stroke s){
        double log = Math.log(aspectRatio(s));
        return log;
    }

    /* angle between endpoints */
    public static double endpointAngle(Stroke s) {
        Point p1 = s.dataPoints.get(0);
        Point p2 = s.dataPoints.get(s.dataPoints.size() - 1);
        return Math.atan2(p2.y_coor - p1.y_coor, p2.x_coor - p1.x_coor);
    }

    /* sharpness of stroke sum of squares of angles */
    public static double strokeSharpness(Stroke s) {
        int n = s.dataPoints.size();
        if(n < 3)
            return 0;
        double sharpness = 0;
        for(int i = 0; i < (n - 2); ++i) {
            Point p1 = s.dataPoints.get(i);
            Point p2 = s.dataPoints.get(i+1);
            Point p3 = s.dataPoints.get(i+2);
            double angle1 = Math.atan2(p2.y_coor - p1.y_coor,
                                       p2.x_coor - p1.x_coor);
            double angle2 = Math.atan2(p3.y_coor - p2.y_coor,
                                       p3.x_coor - p2.x_coor);
            sharpness += (angle1 - angle2) * (angle1 - angle2);
        }
        return sharpness;
    }

    /* log of bounding box area */
    public static double logArea(Stroke s) {
        return Math.log(sizeBoundingBox(s));
    }

    /*Overall Total Tilt   */
    public static double totalTilt(Stroke s) {
        double x=0;
        double y=0;
        double tiltTotal=0;
        double tiltLast=0;
        double tiltCurrent=0;
        x=(double)s.dataPoints.get(0).tilt_x;
        y=(double)s.dataPoints.get(0).tilt_y;
        x=Math.cos(x);
        y=Math.cos(y);
        tiltLast=Math.sqrt((x*x)+(y*y));
        for(int i = 1; i < s.dataPoints.size(); i++) {
            x=(double)s.dataPoints.get(i).tilt_x;
            y=(double)s.dataPoints.get(i).tilt_y;
            x=Math.cos(x);
            y=Math.cos(y);
            tiltCurrent=Math.sqrt((x*x)+(y*y));
            tiltTotal+=Math.abs(tiltCurrent-tiltLast);
            tiltLast=tiltCurrent;
        }
        return tiltTotal;
    }
    
    /* count the number of self-intersections */
    public static int numIntersections(Stroke s) {
        int n = s.dataPoints.size();
        System.out.println("n: " + n);
        int intersections = 0;
        for(int i = 0; i < (n - 20); i+=10) {
            Point pA = s.dataPoints.get(i);
            Point pB = s.dataPoints.get(i+10);
            double xA = pA.x_coor, xB = pB.x_coor;
            double yA = pA.y_coor, yB = pB.y_coor;
            //System.out.println("i: " + i);
            for(int j = (i + 10); j < (n - 10); j+=10) {
                //System.out.println("j: " + j);
                Point pC = s.dataPoints.get(j);
                Point pD = s.dataPoints.get(j+10);
                double xC = pC.x_coor, xD = pD.x_coor;
                double yC = pC.y_coor, yD = pD.y_coor;

                double xBA = xB - xA, yBA = yB - yA;
                double xCD = xC - xD, yCD = yC - yD;
                double xCA = xC - xA, yCA = yC - yA;

                double determinate = xBA * yCD - xCD * yBA;
                //System.out.println("determinate: " + determinate);
                if(Math.abs(determinate) < 1)
                    continue;
                double m1 = yCD / determinate;
                double m2 = -xCD / determinate;
                /*double m3 = -yBA / determinate;
                double m4 = xBA / determinate;*/

                double tParam = m1 * xCA + m2 * yCA;
                if(tParam > 0.01 && tParam < 0.99) {
                    System.out.println("tParam: " + tParam);
                    ++intersections;
                }
            }
        }
        return intersections;
    }
    
    /*Longest Straight*/
    public static double longestStraight(Stroke s) {
        double x1=0;
        double y1=0;
        double x2=0;
        double y2=0;
        double xL, yL;
        double slopeLast=0, slope=0;
        boolean noSlope=false;
        double length=0;
        double lengthMax=0;
        for(int i = 0; i < s.dataPoints.size()-1; i++) {
            x1=(double)s.dataPoints.get(i).x_coor;
            y1=(double)s.dataPoints.get(i).y_coor;
            x2=(double)s.dataPoints.get(i+1).x_coor;
            y2=(double)s.dataPoints.get(i+1).y_coor;
            if(x2-x1==0) //checks for divide by zero
            {                
                if(noSlope) //straight line
                {
                    length+=Math.sqrt(((y2-y1)*(y2-y1))+((x2-x1)*(x2-x1)));
                }
                else
                    length=Math.sqrt(((y2-y1)*(y2-y1))+((x2-x1)*(x2-x1)));   
                slope=0;
                slopeLast=0;          
                             
                noSlope=true;
            }
            else
            {
                noSlope=false;
                slope=(y2-y1)/(x2-x1);
                if(slopeLast==slope) //straight line
                {
                    length+=Math.sqrt(((y2-y1)*(y2-y1))+((x2-x1)*(x2-x1)));
                }
                else
                    length=Math.sqrt(((y2-y1)*(y2-y1))+((x2-x1)*(x2-x1))); 
                slopeLast=slope;
            }

            slopeLast=slope;
            if(length>lengthMax)
                    lengthMax=length;
        }
        return lengthMax;
    }

    /* "centroid" of stroke */
    public static Point centroid(Stroke s) {
        double xMin = 0, xMax = 0, yMin = 0, yMax = 0;
        double x = 0, y = 0;
        for(int i = 0; i < s.dataPoints.size(); ++i) {
            x = s.dataPoints.get(i).x_coor;
            y = s.dataPoints.get(i).y_coor;
            if (i == 0) {
                xMin = xMax = x;
                yMin = yMax = y;
            }
            if(x < xMin) xMin = x;
            if(x > xMax) xMax = x;
            if(y < yMin) yMin = y;
            if(y > yMax) yMax = y;
        }
        double xMed = (xMax + xMin) / 2.0;
        double yMed = (yMax + yMin) / 2.0;
        return new Point(xMed, yMed, s.dataPoints.get(0).time);
    }

    /* distance from center of screen */
    public static double centerDistance(Stroke s) {
        double xDiff = centroid(s).x_coor - 750;
        double yDiff = centroid(s).y_coor - 400;
        return Math.sqrt(xDiff * xDiff + yDiff + yDiff);
    }
    
    /* pressure of second quarter versus last quarter */
    public static double secondQuarterVersusLastQuarterPressure(Stroke s) {
        Stroke first = new Stroke(s,2,4);
        Stroke last = new Stroke(s,4,4);
        return averagePressure(first) - averagePressure(last);
    }
}	

